package com.zrkizzy.module.system.aspect;

import cn.hutool.json.JSONUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.zrkizzy.common.core.constant.HttpConst;
import com.zrkizzy.common.core.domain.response.Result;
import com.zrkizzy.common.core.enums.CommonErrorCode;
import com.zrkizzy.common.core.exception.GlobalException;
import com.zrkizzy.common.core.utils.IdUtil;
import com.zrkizzy.common.core.utils.IpUtil;
import com.zrkizzy.common.core.utils.ServletUtil;
import com.zrkizzy.common.core.utils.StringUtil;
import com.zrkizzy.common.models.domain.system.log.LoginLog;
import com.zrkizzy.common.models.dto.system.common.LoginDTO;
import com.zrkizzy.common.mq.service.IRabbitService;
import com.zrkizzy.common.security.context.SecurityContext;
import com.zrkizzy.common.security.context.UserContext;
import eu.bitwalker.useragentutils.UserAgent;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * 登录日志AOP切面
 *
 * @author zhangrongkang
 * @since 2023/6/20
 */
@Aspect
@Slf4j
@Component
public class LoginLogAspect {

    @Autowired
    private IdUtil idUtil;

    @Autowired
    @Qualifier("loginLogProducer")
    private IRabbitService loginLogProducer;

    /**
     * 用于记录登录时间
     */
    private static final TransmittableThreadLocal<LocalDateTime> TIME_THREAD_LOCAL = new TransmittableThreadLocal<>();

    /**
     * 用于记录登录用户名称
     */
    private static final TransmittableThreadLocal<String> USERNAME_THREAD_LOCAL = new TransmittableThreadLocal<>();

    /**
     * 申明切入点位置
     */
    @Pointcut("@annotation(com.zrkizzy.system.facade.annotation.LoginLog)")
    public void controllerLog() {}

    /**
     * 处理登录请求前执行
     *
     * @param joinPoint 切入点
     */
    @Before("controllerLog()")
    public void beforeLogin(JoinPoint joinPoint) {
        // 用户输入参数仅有用户登录数据传输对象，因此直接获取即可
        LoginDTO loginDTO = JSONUtil.toBean(JSONUtil.parse(joinPoint.getArgs()[0]).toString(), LoginDTO.class);
        // 设置用户登录名称
        if (null != loginDTO) {
            USERNAME_THREAD_LOCAL.set(loginDTO.getUsername());
        }

        // 设置当前登录时间
        TIME_THREAD_LOCAL.set(LocalDateTime.now());
    }

    /**
     * 处理请求结束后执行方法
     *
     * @param joinPoint 切入点
     * @param jsonResult 返回结果
     */
    @AfterReturning(value = "controllerLog()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        // 记录登录日志信息
        handleLoginInfo(joinPoint, null, jsonResult);
    }

    @AfterThrowing(value = "controllerLog()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        // 记录登录日志信息
        handleLoginInfo(joinPoint, e, null);
    }

    /**
     * 添加登录日志到数据库中
     *
     * @param joinPoint 切入点
     * @param e 抛出异常
     * @param jsonResult 方法返回结果
     */
    protected void handleLoginInfo(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
        // 定义登录日志对象
        LoginLog loginLog = new LoginLog();
        try {
            // 转换结果对象
            Result<?> result = null;
            if (null != jsonResult) {
                result = JSONUtil.toBean(JSONUtil.parse(jsonResult).toString(), Result.class);
            }
            // 如果没有登录成功
            if (null != result) {
                // 登录消息提示
                loginLog.setMessage(String.valueOf(result.getMessage()));
                // 登录状态
                loginLog.setStatus(result.getCode().equals(CommonErrorCode.SUCCESS.getCode()) ? Boolean.TRUE : Boolean.FALSE);
            }
            // 判断是否出现异常
            if (null != e) {
                // 根据异常类型来定义返回信息
                if (e instanceof GlobalException) {
                    loginLog.setMessage(((GlobalException) e).getErrorCode().message());
                } else {
                    loginLog.setMessage(CommonErrorCode.INTERNAL_SERVER_ERROR.getMessage());
                }
                // 设置登录状态
                loginLog.setStatus(Boolean.FALSE);
            }
            // 设置登录日志对象实体
            setLoginLogAttribute(loginLog);

            // 推送登录日志到消息队列中
            loginLogProducer.sendMessage(loginLog);
        } catch (Exception exception) {
            log.error("保存登录日志出错: {}", exception.getMessage());
        } finally {
            // 移除当前线程中的变量
            TIME_THREAD_LOCAL.remove();
            USERNAME_THREAD_LOCAL.remove();
            // 清除用户上下文变量
            UserContext.remove();
        }
    }

    /**
     * 设置用户登录日志属性
     *
     * @param loginLog 用户登录日志信息实体类
     */
    private void setLoginLogAttribute(LoginLog loginLog) {
        // 获取请求和响应
        HttpServletRequest request = ServletUtil.getRequest();
        // 登录IP
        String ip = IpUtil.getIpAddress(request);

        loginLog.setLoginIp(ip);
        // IP属地
        loginLog.setLoginLocation(IpUtil.getIpLocation(ip));
        // 获取用户登录设备信息对象
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader(HttpConst.USER_AGENT));
        // 浏览器版本
        loginLog.setBrowser(userAgent.getBrowser().getName());
        // 操作系统
        loginLog.setOs(userAgent.getOperatingSystem().getName());
        // 登录时间
        loginLog.setLoginTime(TIME_THREAD_LOCAL.get());
        // ID
        if (StringUtil.isNoneBlank(SecurityContext.getTraceId())) {
            loginLog.setId(Long.parseLong(SecurityContext.getTraceId()));
        } else {
            loginLog.setId(idUtil.nextId());
        }
        // 用户名
        loginLog.setUsername(USERNAME_THREAD_LOCAL.get());
    }

}
